package com.zhoug.common3.clip;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;

import com.zhoug.common3.utils.LogUtils;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;

/**
 * Created by wenshi on 2019/2/26.
 * Description
 */
public class ClipImageView extends AppCompatImageView implements ViewTreeObserver.OnGlobalLayoutListener {
    private static final String TAG = ">>>CutImageView";
    // 手势帮助类
    private GestureDetector mGestureDetector;
    //缩放帮助类
    private ScaleGestureDetector mScaleGestureDetector;
    private Matrix mMatrix;
    private static final int MIN_LIMIT_BOUNDS = 50;
    private boolean mIsTrans;
    private boolean mIsScale;

    //
    private ClipFrameView mClipFrameView;
    private boolean mClipFrameViewTransEnable = true;
    private boolean mClipFrameViewScaleEnable = true;

    /**
     * 最大缩放值
     */
    private float mMaxScale = 5;
    /**
     * 是否初始化Matrix
     */
    private boolean mIsInitImageMatrix = true;
    /**
     * Drawable 原始宽
     */
    private int mDrawableWidth;
    /**
     * Drawable 原始高
     */
    private int mDrawableHeight;
    /**
     * 默认选择框的宽高比
     */
    private float mFrameRatio=0.75f;

    public ClipImageView(Context context) {
        this(context, null);
    }

    public ClipImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ClipImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScaleGestureDetector = new ScaleGestureDetector(context, mOnScaleGestureListener);
        mGestureDetector = new GestureDetector(context, mSimpleOnGestureListener);

        mMatrix = new Matrix();
        setScaleType(ScaleType.MATRIX);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        LogUtils.d(TAG, "onTouchEvent:");
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 防止父类拦截事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                boolean isOutBounds = boundsCheck();
                if (!isOutBounds) {
                    setLimitBounds();
                }
                mIsTrans = false;
                mIsScale = false;
                mClipFrameView.setScaleEnabled(mClipFrameViewScaleEnable);
                mClipFrameView.setTranslateEnabled(mClipFrameViewTransEnable);
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }

        if (!mIsScale && mGestureDetector.onTouchEvent(event)) {
            return true;
        }
        mScaleGestureDetector.onTouchEvent(event);
        return true;
    }

    private float mPreScaleFactor = 1.0f;
    // 缩放手势(两个手指)中点位置
    private float mLastFocusX;
    private float mLastFocusY;
    // 处理双指的缩放
    private ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = new ScaleGestureDetector.OnScaleGestureListener() {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            if (null == getDrawable() || mMatrix == null) {
                // 如果返回true那么detector就会重置缩放事件
                return true;
            }
            mIsScale = true;
            mClipFrameView.setScaleEnabled(false);
            mClipFrameView.setTranslateEnabled(false);
            // 缩放因子,缩小小于1,放大大于1,是相对于开始缩放时的大小
            float scaleFactor = mScaleGestureDetector.getScaleFactor();
            LogUtils.d(TAG, "onScale:scaleFactor=" + scaleFactor);
            // 缩放因子偏移量 大于0放大,小于0缩小
            float deltaFactor = scaleFactor - mPreScaleFactor;
            LogUtils.d(TAG, "onScale:scaleFactor=" + scaleFactor + ",deltaFactor=" + deltaFactor);

            if (scaleFactor != 1.0F && deltaFactor != 0F) {
                mMatrix.getValues(mMatrixValues);
                float curScaleX = _getScale();
                //放大
                if (deltaFactor > 0) {
                    LogUtils.d(TAG, "onScale:放大");
                    if (curScaleX < mMaxScale) {
                        if (curScaleX * (deltaFactor + 1F) > mMaxScale) {
                            deltaFactor = mMaxScale / curScaleX - 1;
                        }
                    } else {
                        deltaFactor = 0F;
                    }
                } else {
                    //缩小
                    LogUtils.d(TAG, "onScale:缩小");
                    RectF frameRect = mClipFrameView.getFrameRect();
                    float minWidth = frameRect.right - frameRect.left;
                    float minHeight = frameRect.bottom - frameRect.top;
                    float minScaleX = minWidth / mDrawableWidth;
                    float minScaleY = minHeight / mDrawableHeight;
                    float minScale = Math.max(minScaleX, minScaleY);
                    if (curScaleX > minScale) {
                        if (curScaleX * (deltaFactor + 1F) < minScale) {
                            deltaFactor = minScale / curScaleX - 1;
                        }
                    } else {
                        deltaFactor = 0F;
                    }
                }
                if (deltaFactor != 0F) {
                    mMatrix.postScale(deltaFactor + 1F, deltaFactor + 1F, mLastFocusX = mScaleGestureDetector.getFocusX(),
                            mLastFocusY = mScaleGestureDetector.getFocusY());
                    setImageMatrix(mMatrix);
                }
            }
            mPreScaleFactor = scaleFactor;
            return false;
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            // 注意返回true
            LogUtils.d(TAG, "onScaleBegin:开始缩放");
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            LogUtils.d(TAG, "onScaleEnd:缩放结束");
        }
    };

    // 处理手指滑动
    private GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() {

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (e1.getPointerCount() == e2.getPointerCount() && e1.getPointerCount() == 1) {
                mIsTrans = true;
                mClipFrameView.setScaleEnabled(false);
                mClipFrameView.setTranslateEnabled(false);
                translate(distanceX, distanceY);
                return true;
            }
            return false;
        }
    };

    /**
     * 移动
     *
     * @param disX >0左移
     * @param disY >0 上移
     */
    private void translate(float disX, float disY) {
//        Logger.d(TAG, "translate:disX=" + disX + ",disY=" + disY);
        int width = getWidth();
        int height = getHeight();
        RectF frameRect = mClipFrameView.getFrameRect();
        int limitLeft = 0;
        int limitTop = 0;
        int limitRight = width;
        int limitBottom = height;
        if (frameRect != null) {
            limitLeft = (int) frameRect.left;
            limitTop = (int) frameRect.top;
            limitRight = (int) frameRect.right;
            limitBottom = (int) frameRect.bottom;
        }
        LogUtils.d(TAG, "translate:limit(" + limitLeft + "," + limitTop + "," + limitRight + "," + limitBottom + ")");
        mMatrix.getValues(mMatrixValues);
        float transX = mMatrixValues[Matrix.MTRANS_X];
        float transY = mMatrixValues[Matrix.MTRANS_Y];
        float left = transX;
        float top = transY;
        float right = left + _getScale() * mDrawableWidth;
        float bottom = top + _getScale() * mDrawableHeight;
        LogUtils.d(TAG, "translate:bitmap(" + left + "," + top + "," + right + "," + bottom + ")");

        //右移
        if (disX < 0) {
            if (left - disX > limitLeft) {
                disX = transX - limitLeft;
            }
            LogUtils.d(TAG, "translate:右移:" + disX);
        } else {
            //左移
            if (right - disX < limitRight) {
                disX = right - limitRight;
            }
            LogUtils.d(TAG, "translate:左移:" + disX);
        }
        //上移
        if (disY > 0) {
            if (bottom - disY < limitBottom) {
                disY = bottom - limitBottom;
            }
            LogUtils.d(TAG, "translate:上移:" + disX);
        } else {
            //下移
            if (top - disY > limitTop) {
                disY = top - limitTop;
            }
            LogUtils.d(TAG, "translate:下移:" + disY);
        }
        if (disX != 0 || disY != 0) {
            mMatrix.postTranslate(-disX, -disY);
            setImageMatrix(mMatrix);
        }
    }


    public static int dip2px(Context context, float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 会调用setImageDrawable
     *
     * @param resId
     */
    @Override
    public void setImageResource(int resId) {
        LogUtils.d(TAG, "setImageResource:");
        super.setImageResource(resId);
    }

    /**
     * 会调用 setImageDrawable
     *
     * @param bm
     */
    @Override
    public void setImageBitmap(Bitmap bm) {
        if(bm!=null){
            LogUtils.d(TAG, "setImageBitmap:width:"+bm.getWidth()+",height:"+bm.getHeight());
        }
        super.setImageBitmap(bm);
    }


    @Override
    public void setImageDrawable(@Nullable Drawable drawable) {
        LogUtils.d(TAG, "setImageDrawable:");
        super.setImageDrawable(drawable);
        mIsInitImageMatrix = true;
        initImageMatrix();
    }


    @Override
    public void setImageURI(@Nullable Uri uri) {
        LogUtils.d(TAG, "setImageURI:");
        super.setImageURI(uri);
    }


    public void setClipFrameView(ClipFrameView clipFrameView) {
        this.mClipFrameView = clipFrameView;
        mClipFrameViewTransEnable = mClipFrameView.isTranslateEnabled();
        mClipFrameViewScaleEnable = mClipFrameView.isScaleEnabled();
    }


    /**
     * 初始化矩阵,根据图片尺寸缩放和平移,居中显示
     */
    private void initImageMatrix() {
        if (mIsInitImageMatrix) {
            Drawable drawable = getDrawable();
            if (null == drawable) {
                LogUtils.d(TAG, "initImageMatrix:drawable is null");
                return;
            }
            // 获取控件的宽度和高度
            int viewWidth = getWidth();
            int viewHeight = getHeight();
            if (viewWidth <= 0 || viewHeight <= 0) {
                LogUtils.d(TAG, "initImageMatrix:未测量尺寸");
                return;
            }
            LogUtils.d(TAG, "initImageMatrix:");
            mIsInitImageMatrix = false;
            mMatrix.reset();

            // 图片的固定宽度  高度
            // 获取图片的宽度和高度
            mDrawableWidth = drawable.getIntrinsicWidth();
            mDrawableHeight = drawable.getIntrinsicHeight();
            LogUtils.d(TAG, "initImageMatrix:drawableWidth=" + mDrawableWidth + ",drawableHeight=" + mDrawableHeight);
            // 将图片移动到屏幕的中点位置
            float dx = (viewWidth - mDrawableWidth) >> 1;
            float dy = (viewHeight - mDrawableHeight) >> 1;
            LogUtils.d(TAG, "initImageMatrix:dx=" + dx + ",dy=" + dy);
            // 平移居中
            mMatrix.postTranslate(dx, dy);
            // 缩放
            float scaleX = viewWidth * 1F / mDrawableWidth;
            float scaleY = viewHeight * 1F / mDrawableHeight;
            float initScale = Math.min(scaleX, scaleY);
            //初始最小缩放是宽高一半
            float minScale = Math.max(viewWidth * 0.5F / mDrawableWidth, viewHeight * 0.5F / mDrawableHeight);
            if (initScale < minScale) {
                initScale = minScale;
            }

            mMaxScale = Math.max(initScale + 3, Math.max(scaleX, scaleY));
            LogUtils.d(TAG, "initImageMatrix:initScale=" + initScale + ",mMaxScale=" + mMaxScale);

            mMatrix.postScale(initScale, initScale, viewWidth >> 1, viewHeight >> 1);
            setImageMatrix(mMatrix);
            setLimitBounds();
            mClipFrameView.fitLimitBounds(mFrameRatio, 0.85f);
        }
    }



    /**
     * scale_x   skew_x     trans_x
     * skew_y    scale_y    trans_y
     * persp_0   persp_1    persp_2
     */
    float[] mMatrixValues = new float[9];

    @Override
    public void setImageMatrix(Matrix matrix) {
        super.setImageMatrix(matrix);
        mMatrix = matrix;
//        printMatrix();
    }

    private void printMatrix() {
        /**
         * scale_x   skew_x     trans_x
         * skew_y    scale_y    trans_y
         * persp_0   persp_1    persp_2
         */
        mMatrix.getValues(mMatrixValues);
        for (int i = 0; i < 3; i++) {
            LogUtils.d(TAG, "" + mMatrixValues[i * 3] + " " + mMatrixValues[i * 3 + 1] + " " + mMatrixValues[i * 3 + 2]);
        }
    }

    private void setLimitBounds() {
        mMatrix.getValues(mMatrixValues);
        mClipFrameView.setLimitBounds(_getBoundLeft(), _getBoundTop(), _getBoundRight(), _getBoundBottom());
    }

    private int _getBoundLeft() {
        float transX = _getTransX();
        return transX > MIN_LIMIT_BOUNDS ? (int) transX : MIN_LIMIT_BOUNDS;
    }

    private int _getBoundTop() {
        float transY = _getTransY();
        return transY > MIN_LIMIT_BOUNDS ? (int) transY : MIN_LIMIT_BOUNDS;
    }

    private int _getBoundRight() {
        float right = _getImageRight();
        int width = getWidth();
        int boundRight = (int) (width - right);
        return Math.max(boundRight, MIN_LIMIT_BOUNDS);
    }

    private int _getBoundBottom() {
        float bottom = _getImageBottom();
        int height = getHeight();
        int boundBottom = (int) (height - bottom);
        return Math.max(boundBottom, MIN_LIMIT_BOUNDS);
    }

    //图片经过缩放,平移后当前显示的位置
    //left
    private float _getImageLeft() {
        return _getTransX();
    }

    //top
    private float _getImageTop() {
        return _getTransY();
    }

    //right
    private float _getImageRight() {
        return _getImageLeft() + _getScale() * mDrawableWidth;
    }

    //bottom
    private float _getImageBottom() {
        return _getImageTop() + _getScale() * mDrawableHeight;
    }


    private float _getTransX() {
        return mMatrixValues[Matrix.MTRANS_X];
    }

    private float _getTransY() {
        return mMatrixValues[Matrix.MTRANS_Y];
    }

    //x,y缩放一样
    private float _getScale() {
        return mMatrixValues[Matrix.MSCALE_X];
    }


    @Override
    public void onGlobalLayout() {
        initImageMatrix();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        LogUtils.d(TAG, "onAttachedToWindow:");
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    /**
     * 边界检测 越界则开启动画自动对齐
     *
     * @return isOutBounds 是否越界
     */
    private boolean boundsCheck() {
        RectF frameRect = mClipFrameView.getFrameRect();
        mMatrix.getValues(mMatrixValues);
        float imageLeft = _getImageLeft();
        float imageTop = _getImageTop();
        float imageRight = _getImageRight();
        float imageBottom = _getImageBottom();
        boolean isOutBounds = false;
        if (imageLeft > frameRect.left) {
            isOutBounds = true;
            // 左越界
            startBoundAnimator(imageLeft, frameRect.left, true);
        }

        if (imageTop > frameRect.top) {
            isOutBounds = true;
            // 上越界
            startBoundAnimator(imageTop, frameRect.top, false);
        }

        if (imageRight < frameRect.right) {
            isOutBounds = true;
            // 右越界
            float disX = frameRect.right - imageRight;
            if (imageLeft + disX > frameRect.left) {
                disX = frameRect.left - imageLeft;
            }
            startBoundAnimator(imageLeft, imageLeft + disX, true);
        }

        if (imageBottom < frameRect.bottom) {
            isOutBounds = true;
            // 下越界
            float disY = frameRect.bottom - imageBottom;
            if (imageTop + disY > frameRect.top) {
                disY = frameRect.top - imageTop;
            }
            startBoundAnimator(imageTop, imageTop + disY, false);
        }

        return isOutBounds;
    }

    private ValueAnimator boundAnimator;

    public void setFrameRatio(float frameRatio) {
        this.mFrameRatio = frameRatio;
    }


    /**
     * 开始越界动画
     *
     * @param start      开始点坐标
     * @param end        结束点坐标
     * @param horizontal 是否水平动画  true 水平动画 false 垂直动画
     */
    private void startBoundAnimator(float start, float end, final boolean horizontal) {
        boundAnimator = ValueAnimator.ofFloat(start, end);
        boundAnimator.setDuration(200);
        boundAnimator.setInterpolator(new LinearInterpolator());
        boundAnimator.addUpdateListener(animation -> {
            float v = (float) animation.getAnimatedValue();
            mMatrix.getValues(mMatrixValues);
            mMatrixValues[horizontal ? Matrix.MTRANS_X : Matrix.MTRANS_Y] = v;

            mMatrix.setValues(mMatrixValues);
            setImageMatrix(mMatrix);
        });
        boundAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                setLimitBounds();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        boundAnimator.start();
    }


    public Bitmap getClipImage() {
        RectF frameRect = mClipFrameView.getFrameRect();
        printMatrix();
        LogUtils.d(TAG, "getClipImage:frameRect=" + frameRect + ",width=" + (frameRect.right - frameRect.left) + ",height=" + (frameRect.bottom - frameRect.top));
        Drawable drawable = getDrawable();
        if (drawable instanceof BitmapDrawable) {
            LogUtils.d(TAG, "Drawable:" + mDrawableWidth + "," + mDrawableHeight);
            Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            LogUtils.d(TAG, "bitmap:" + bitmap.getWidth() + "," + bitmap.getHeight());
            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mMatrix, false);
            int bitmapWidth = bitmap.getWidth();
            int bitmapHeight = bitmap.getHeight();
            LogUtils.d(TAG, "mMatrix bitmap:" + bitmapWidth + "," + bitmapHeight);
            mMatrix.getValues(mMatrixValues);
            float transX = mMatrixValues[Matrix.MTRANS_X];
            float transY = mMatrixValues[Matrix.MTRANS_Y];


            int x = (int) (frameRect.left - transX);
            if (x < 0) {
                x = 0;
            }
            int y = (int) (frameRect.top - transY);
            if (y < 0) {
                y = 0;
            }
            int width = (int) (frameRect.right - frameRect.left);
            if (x + width > bitmapWidth) {
                width = bitmapWidth - x;
            }
            int height = (int) (frameRect.bottom - frameRect.top);
            if (y + height > bitmapHeight) {
                height = bitmapHeight - y;
            }
            LogUtils.d(TAG, "getClipImage:x=" + x + ",y=" + y + ",width=" + width + ",height=" + height);
            bitmap = Bitmap.createBitmap(bitmap, x, y, width, height);
            return bitmap;
        }

        return null;
    }

}
